﻿using ST.Common.Util;
using ST.Common.Util.Security;
using ST.WX.Common.Enums;
using System;
using System.Collections.Generic;
using System.Net;
using System.Security.Cryptography;
using System.Text;
using System.Xml;

namespace ST.WX.Common.Model
{
	/// <summary>
	/// 微信支付协议接口数据类，所有的API接口通信都依赖这个数据结构，
	/// 在调用接口之前先填充各个字段的值，然后进行接口通信，
	/// 这样设计的好处是可扩展性强，用户可随意对协议进行更改而不用重新设计数据结构，
	/// 还可以随意组合出不同的协议数据包，不用为每个协议设计一个数据包结构
	/// </summary>
	public class WXApiData
	{
		/// <summary>
		/// 采用排序的Dictionary的好处是方便对数据包进行签名，不用再签名之前再做一次排序
		/// </summary>
		private SortedDictionary<string, object> paras;

		public WXApiData()
		{
			paras = new SortedDictionary<string, object>();
		}

		/// <summary>
		/// 设置某个字段的值
		/// </summary>
		/// <param name="key"></param>
		/// <param name="value"></param>
		public void SetValue(string key, object value)
		{
			paras[key] = value;
		}

		/// <summary>
		/// 根据字段名获取某个字段的值
		/// </summary>
		/// <param name="key"></param>
		/// <returns></returns>
		public object GetValue(string key)
		{
			object o = null;
			paras.TryGetValue(key, out o);
			return o;
		}

		/// <summary>
		/// 判断某个字段是否已设置
		/// </summary>
		/// <param name="key"></param>
		/// <returns></returns>
		public bool IsSet(string key)
		{
			object o = null;
			paras.TryGetValue(key, out o);
			if (null != o)
				return true;
			else
				return false;
		}

		/// <summary>
		/// 将Dictionary转成xml
		/// </summary>
		/// <returns></returns>
		public string ToXml()
		{
			//数据为空时不能转化为xml格式
			if (0 == paras.Count)
			{
				throw new WXException("请求参数数据为空!");
			}

			string xml = "<xml>";
			foreach (KeyValuePair<string, object> p in paras)
			{
				//字段值不能为null，会影响后续流程
				if (p.Value == null)
				{
					throw new WXException("请求参数" + p.Key + "为空");
				}

				if (p.Value.GetType() == typeof(int))
				{
					xml += "<" + p.Key + ">" + p.Value + "</" + p.Key + ">";
				}
				else if (p.Value.GetType() == typeof(string))
				{
					xml += "<" + p.Key + ">" + "<![CDATA[" + p.Value + "]]></" + p.Key + ">";
				}
				else//除了string和int类型不能含有其他数据类型
				{
					throw new WXException("请求参数" + p.Key + "数据类型错误!");
				}
			}
			xml += "</xml>";
			return xml;
		}

		/// <summary>
		/// 将xml转为内部对象并返回
		/// </summary>
		/// <param name="xml"></param>
		/// <returns></returns>
		public SortedDictionary<string, object> FromXml(string xml, string mchKey, WXSignType signType = WXSignType.SHA256)
		{
			if (string.IsNullOrEmpty(xml))
			{
				throw new WXException("xml为空!");
			}

			XmlDocument xmlDoc = new XmlDocument();
			xmlDoc.XmlResolver = null;
			xmlDoc.LoadXml(xml);
			XmlNode xmlNode = xmlDoc.FirstChild;//获取到根节点<xml>
			XmlNodeList nodes = xmlNode.ChildNodes;
			foreach (XmlNode xn in nodes)
			{
				XmlElement xe = (XmlElement)xn;
				paras[xe.Name] = xe.InnerText;//获取xml的键值对到内部的数据中//todo:要不要判读存不存在键值
			}

			try
			{
				if ((string)paras["return_code"] != "SUCCESS") //错误时没有签名
				{
					return paras;
				}
				CheckPaySign(mchKey, signType);//验证签名,不通过会抛异常
			}
			catch (WXException ex)
			{
				throw new WXException(ex.Message);
			}

			return paras;
		}

		/// <summary>
		/// Dictionary格式转化成url参数格式
		/// </summary>
		/// <returns></returns>
		public string ToUrl()
		{
			string buff = string.Empty;//todo：没有使用StringBuilder
			foreach (KeyValuePair<string, object> pair in paras)
			{
				if (pair.Value == null)
				{
					throw new WXException("WXApiData内部含有值为null的字段!");
				}

				if (pair.Key != "sign" && pair.Value.ToString() != "")
				{
					buff += pair.Key + "=" + pair.Value + "&";
				}
			}
			buff = buff.TrimEnd('&');
			return buff;
		}

		/// <summary>
		/// Dictionary格式转化成json格式
		/// </summary>
		/// <returns></returns>
		public string ToJson()
		{
			return Newtonsoft.Json.JsonConvert.SerializeObject(paras);
		}

		/// <summary>
		/// 格式化成能在Web页面上显示的结果（因为web页面上不能直接输出xml格式的字符串）
		/// </summary>
		/// <returns></returns>
		public string ToPrintStr()
		{
			string str = "";
			foreach (KeyValuePair<string, object> pair in paras)
			{
				if (pair.Value == null)
				{
					throw new WXException("WxPayData内部含有值为null的字段!");
				}

				str += string.Format("{0}={1}\n", pair.Key, pair.Value.ToString());
			}
			str = WebUtility.HtmlEncode(str);
			return str;
		}

		/// <summary>
		/// 生成签名，详见签名生成算法（sign字段不参加签名）
		/// </summary>
		/// <param name="signType"></param>
		/// <returns></returns>
		public string MakePaySign(string mchKey, WXSignType signType = WXSignType.SHA256)
		{
			//转url格式
			string str = ToUrl();
			//在string后加入商户支付密钥
			str += "&key=" + mchKey;
			switch (signType)
			{
				case WXSignType.MD5:
					return EncryptHelper.MD5Hash(str, true);//所有字符转为大写
				case WXSignType.SHA256:
					return EncryptHelper.SHA256Hash(str, mchKey, false);//todo:微信官方使用的是Encoding.Default进行编码，并且是小写形式
				default:
					throw new WXException("不支持的签名类型");
			}
		}

		/// <summary>
		/// 验证签名
		/// </summary>
		/// <param name="mchKey"></param>
		/// <param name="signType"></param>
		/// <returns></returns>
		public bool CheckPaySign(string mchKey, WXSignType signType = WXSignType.SHA256)
		{
			string sign = Convert.ToString(GetValue("sign"));//获取接收到的签名
			if (string.IsNullOrEmpty(sign))
			{
				throw new WXException("签名存在但为空");
			}

			//在本地计算新的签名
			string calcSign = MakePaySign(mchKey, signType);

			if (string.Compare(calcSign, sign, true) == 0)
			{
				return true;
			}
			throw new WXException("签名验证不一致!");
		}

		/// <summary>
		/// 获取内部对象数据
		/// </summary>
		/// <returns></returns>
		public SortedDictionary<string, object> GetValues()
		{
			return paras;
		}
	}
}
